package webhook

import (
	"fmt"
	"go.uber.org/zap"
	admissionv1 "k8s.io/api/admission/v1"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"strings"
)

func validationRequired(metadata *metav1.ObjectMeta) bool {
	zap.L().Debug(funcParams, zap.Any("metadata", metadata))
	required := admissionRequired(annotationValidateKey, metadata)
	zap.L().Info(fmt.Sprintf("Validation admission required for %v/%v: %v", metadata.Namespace, metadata.Name, required))
	return required
}

// validate validating webhook 入口
func (whsvr *WhServer) validate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
	req := ar.Request
	var (
		resourceNamespace string
		resourceName      string
		objectMeta        *metav1.ObjectMeta
		containers        []corev1.Container
	)
	zap.L().Info(
		"Admission validate start",
		zap.Any("kind", req.Kind),
		zap.Any("namespace", req.Namespace),
		zap.Any("name", req.Name),
		zap.Any("operation", req.Operation),
		zap.Any("user_info", req.UserInfo),
	)
	kind := req.Kind.Kind

	switch kind {
	// 根据资源类型kind, 解析req中的资源, 取出containers, 交给validateContainersImage进行校验
	case "Deployment":
		var deployment appsv1.Deployment
		if err := parseResource(req.Object.Raw, &deployment); err != nil {
			return buildErrorResponse(500, err.Error())
		}
		resourceName, resourceNamespace, objectMeta = deployment.Name, deployment.Namespace, &deployment.ObjectMeta
		containers = deployment.Spec.Template.Spec.Containers
	case "StatefulSet":
		var statefulSet appsv1.StatefulSet
		if err := parseResource(req.Object.Raw, &statefulSet); err != nil {
			return buildErrorResponse(500, err.Error())
		}
		resourceName, resourceNamespace, objectMeta = statefulSet.Name, statefulSet.Namespace, &statefulSet.ObjectMeta
		containers = statefulSet.Spec.Template.Spec.Containers
	case "DaemonSet":
		var daemonSet appsv1.DaemonSet
		if err := parseResource(req.Object.Raw, &daemonSet); err != nil {
			return buildErrorResponse(500, err.Error())
		}
		resourceName, resourceNamespace, objectMeta = daemonSet.Name, daemonSet.Namespace, &daemonSet.ObjectMeta
		containers = daemonSet.Spec.Template.Spec.Containers
	}
	if !validationRequired(objectMeta) {
		zap.L().Info(fmt.Sprintf("Skipping validation for %s/%s due to policy check", resourceNamespace, resourceName))
		return &admissionv1.AdmissionResponse{Allowed: true}
	}
	return whsvr.validateContainers(containers)
}

// validateContainers 校验Container的镜像与安全上下文, 构建AdmissionResponse
func (whsvr *WhServer) validateContainers(containers []corev1.Container) *admissionv1.AdmissionResponse {
	zap.L().Debug(funcParams, zap.Any("containers", containers))
	var (
		notPrivateRepo  = "Image is not in private repo"
		runAsPrivileged = "Run as privileged is not allowed"
	)
	for _, container := range containers {
		// 镜像是否为私有镜像仓库中的镜像
		name := container.Name
		image := container.Image
		if !strings.HasPrefix(image, whsvr.PrivateRepo) {
			zap.L().Error(
				notPrivateRepo,
				zap.String("container_name", name),
				zap.String("image", image),
			)
			return buildErrorResponse(402, notPrivateRepo)
		}

		// 是否存在以特权模式运行的容器
		if sc := container.SecurityContext; sc != nil && *sc.Privileged {
			zap.L().Error(
				runAsPrivileged,
				zap.String("container_name", name),
				zap.Any("security_context", sc),
			)
			return buildErrorResponse(403, runAsPrivileged)
		}
	}
	return &admissionv1.AdmissionResponse{Allowed: true}
}
